﻿namespace Pub.Class
{
    using System;
    using System.Data;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Runtime.CompilerServices;

    public class IDataReaderEntityBuilder<Entity>
    {
        private static readonly MethodInfo getValueMethod;
        private Load<Entity> handler;
        private static readonly MethodInfo isDBNullMethod;

        static IDataReaderEntityBuilder()
        {
            IDataReaderEntityBuilder<Entity>.getValueMethod = typeof(IDataRecord).GetMethod("get_Item", new Type[] { typeof(int) });
            IDataReaderEntityBuilder<Entity>.isDBNullMethod = typeof(IDataRecord).GetMethod("IsDBNull", new Type[] { typeof(int) });
        }

        private IDataReaderEntityBuilder()
        {
        }

        public Entity Build(IDataRecord dataRecord)
        {
            return this.handler(dataRecord);
        }

        public static IDataReaderEntityBuilder<Entity> CreateBuilder(IDataRecord dataRecord)
        {
            IDataReaderEntityBuilder<Entity> builder = new IDataReaderEntityBuilder<Entity>();
            DynamicMethod method = new DynamicMethod("IDataReaderDynamicCreateEntity", typeof(Entity), new Type[] { typeof(IDataRecord) }, typeof(Entity), true);
            ILGenerator iLGenerator = method.GetILGenerator();
            LocalBuilder local = iLGenerator.DeclareLocal(typeof(Entity));
            iLGenerator.Emit(OpCodes.Newobj, typeof(Entity).GetConstructor(Type.EmptyTypes));
            iLGenerator.Emit(OpCodes.Stloc, local);
            for (int i = 0; i < dataRecord.FieldCount; i++)
            {
                PropertyInfo property = typeof(Entity).GetProperty(dataRecord.GetName(i));
                Label label = iLGenerator.DefineLabel();
                if (property.IsNotNull() && property.GetSetMethod().IsNotNull())
                {
                    iLGenerator.Emit(OpCodes.Ldarg_0);
                    iLGenerator.Emit(OpCodes.Ldc_I4, i);
                    iLGenerator.Emit(OpCodes.Callvirt, IDataReaderEntityBuilder<Entity>.isDBNullMethod);
                    iLGenerator.Emit(OpCodes.Brtrue, label);
                    iLGenerator.Emit(OpCodes.Ldloc, local);
                    iLGenerator.Emit(OpCodes.Ldarg_0);
                    iLGenerator.Emit(OpCodes.Ldc_I4, i);
                    iLGenerator.Emit(OpCodes.Callvirt, IDataReaderEntityBuilder<Entity>.getValueMethod);
                    iLGenerator.Emit(OpCodes.Unbox_Any, property.PropertyType);
                    iLGenerator.Emit(OpCodes.Callvirt, property.GetSetMethod());
                    iLGenerator.MarkLabel(label);
                }
            }
            iLGenerator.Emit(OpCodes.Ldloc, local);
            iLGenerator.Emit(OpCodes.Ret);
            builder.handler = (Load<Entity>) method.CreateDelegate(typeof(Load<Entity>));
            return builder;
        }

        private delegate Entity Load<T>(IDataRecord dataRecord);
    }
}

